package org.raftkv.raft.core.node;


import org.raftkv.commons.selector.Channel;
import org.raftkv.raft.core.Config;
import org.raftkv.raft.core.node.enums.SnapshotStatus;
import org.raftkv.raft.core.rawnode.RawNode;
import org.raftkv.raft.core.status.Status;
import org.raftkv.raft.exceptions.RaftException;
import org.raftkv.raft.pb.ConfState;
import org.raftkv.raft.pb.IConfChange;
import org.raftkv.raft.pb.Message;

import java.util.List;

/**
 * Node 表示 Raft 集群中的一个节点
 *
 * @author wzy
 */
public interface Node {

    /**
     * tick() 函数将会增加内部的逻辑时钟周期。
     * <br/>
     * 选举超时以及心跳超时都是以 tick() 为单位。
     */
    void tick();

    /**
     * campaign() 函数使节点过渡到 Candidate 状态并开始竞选成为 Leader。
     */
    void campaign();

    /**
     * propose() 函数提议写入数据到日志中。
     * <br/>
     * 注意提案可能会丢失而不通知，用户需要确保重试
     */
    void propose(byte[] data);

    /**
     * 将消息加入状态机执行
     *
     * @param msg 执行的消息
     */
    void step(Message msg);

    /**
     * 这里是核心函数，将返回 Ready 的 Channel，应用层需要关注这个 Channel，当发生变更时将其中的数据进行操作
     */
    Channel<Ready> ready();

    /**
     * 该函数是当使用者已经将上一次 Ready 数据处理之后，调用该函数告诉 Raft 库可以进行下一步的操作
     */
    void advance();

    /**
     * 提交日志变更
     */
    void proposeConfChange(IConfChange cc);

    /**
     * 一致性读相关
     *
     * @param readCtx 读请求id
     */
    void readIndex(byte[] readCtx);

    Status status();

    /**
     * 提交配置变更
     *
     * @return 当前配置状态
     */
    ConfState applyConfChange(IConfChange cc);

    void transferLeaderShip(long lead, long transferee);

    /**
     * stop() 执行节点的任何必要终止
     */
    void stop();

    void reportUnreachable(long id);

    void reportSnapshot(long id, SnapshotStatus status);

    static Node startNode(Config config, List<Peer> peers) {
        if (peers.size() == 0) {
            throw new RaftException("no peers given; use RestartNode instead");
        }

        RawNode rn = RawNode.newRawNode(config);
        rn.bootstrap(peers);

        DefaultNode node = DefaultNode.newNode(rn);

        new Thread(node::run);

        return node;
    }

    static Node restartNode(Config c) {
        RawNode rn = RawNode.newRawNode(c);
        DefaultNode node = DefaultNode.newNode(rn);

        new Thread(node::run);

        return node;
    }

}
